package com.lyw.mall.gateway.config;

import com.lyw.mall.gateway.filters.JwtTokenAuthenticationFilter;
import com.lyw.mall.gateway.security.*;
import com.lyw.mall.gateway.service.impl.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.DelegatingReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsConfigurationSource;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.LinkedList;

/**
 * @version 1.0
 * @author: liyawei
 * @createTime: 2023-01-10 15:40
 * @description security 配置
 */
@EnableWebFluxSecurity
@Configuration
public class MySecurityConfiguration{

    /*@Autowired
    private DefaultAuthenticationSuccessHandler serverAuthenticationSuccessHandler;
    @Autowired
    private DefaultAuthenticationFailureHandler defaultAuthenticationFailureHandler;
    */
    @Autowired
    private DefaultAuthenticationEntryPoint defaultAuthenticationEntryPoint;
    @Autowired
    private DefaultAccessDeniedHandler defaultAccessDeniedHandler;
    @Resource
    private DefaultAuthorizationManager defaultAuthorizationManager;

    @Resource
    private TokenAuthenticationManager tokenAuthenticationManager;

    @Autowired
    private DefaultSecurityContextRepository defaultSecurityContextRepository;

    @Resource
    private UserDetailsServiceImpl userDetailsService;

    private static final String[] AUTH_WHITELIST = new String[]{"/admin/admin/list"};

    private CorsConfigurationSource corsConfigurationSource() {
        CorsConfigurationSource source=new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");    //同源配置，*表示任何请求都视为同源，若需指定ip和端口可以改为如“localhost：8080”，多个以“，”分隔；
        corsConfiguration.addAllowedHeader("*");//header，允许哪些header，本案中使用的是token，此处可将*替换为token；
        corsConfiguration.addAllowedMethod("*");    //允许的请求方法，PSOT、GET等
        ((UrlBasedCorsConfigurationSource) source).registerCorsConfiguration("/**",corsConfiguration); //配置允许跨域访问的url
        return source;
    }

    /*@Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity httpSecurity) {
        httpSecurity
                // 登录认证处理
                .authenticationManager(reactiveAuthenticationManager())
                .securityContextRepository(defaultSecurityContextRepository)
                // 请求拦截处理
                .authorizeExchange(exchange -> exchange
                        .pathMatchers(HttpMethod.OPTIONS).permitAll()
                        .anyExchange().authenticated()
                )
                .formLogin()
                // 自定义处理
                .authenticationSuccessHandler(serverAuthenticationSuccessHandler)
                .authenticationFailureHandler(defaultAuthenticationFailureHandler)
                .and()
                .exceptionHandling()
                .authenticationEntryPoint(defaultAuthenticationEntryPoint)
                .and()
                .exceptionHandling()
                .accessDeniedHandler(defaultAccessDeniedHandler)
                .and()
                .addFilterAt(new JwtTokenAuthenticationFilter(userDetailsService), SecurityWebFiltersOrder.HTTP_BASIC)
                .csrf().disable()
                .cors()
                .configurationSource(corsConfigurationSource())
        ;
        return httpSecurity.build();
    }*/

    @Bean
    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
        http
                .authenticationManager(reactiveAuthenticationManager())
                .securityContextRepository(defaultSecurityContextRepository)
                .authorizeExchange(exchange -> exchange // 请求拦截处理
                        // 白名单
                        .pathMatchers(AUTH_WHITELIST).permitAll()
                        .pathMatchers(HttpMethod.OPTIONS).permitAll()
                        // 其余url都要认证才能访问
                        .anyExchange().authenticated()
                )
                .formLogin().disable()
                .httpBasic()
                .and()
                // 确保对我们应用程序的任何请求都需要对用户进行身份验
                // 其他请求都需要认证
                .addFilterAt(new JwtTokenAuthenticationFilter(userDetailsService), SecurityWebFiltersOrder.HTTP_BASIC)
                .csrf().disable()
                .cors()
                .configurationSource(corsConfigurationSource())
        ;
        return http.build();
    }

        /*http.csrf(ServerHttpSecurity.CsrfSpec::disable)
                .httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
                .authenticationManager(reactiveAuthenticationManager)
                .exceptionHandling()
                .authenticationEntryPoint(customHttpBasicServerAuthenticationEntryPoint)// 自定义authenticationEntryPoint
                .accessDeniedHandler((swe, e) -> {
                    swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
                    return swe.getResponse().writeWith(Mono.just(new DefaultDataBufferFactory().wrap("FORBIDDEN".getBytes())));
                })
                .and()
                .securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
                .authorizeExchange()
                .pathMatchers(excludeAuthPages).permitAll()// 白名单
                .pathMatchers(HttpMethod.OPTIONS).permitAll()// option请求默认放行
                .anyExchange()
                .authenticated()
                .and()
                .formLogin()
                .loginPage("/auth/login")
                .authenticationSuccessHandler(authenticationSuccessHandler)// 自定义authenticationSuccessHandler
                .authenticationFailureHandler(authenticationFailHandler)// 自定义authenticationFailureHandler
                .and()
                .logout()
                .logoutUrl("/auth/logout")
                .logoutSuccessHandler(timingLogoutSuccessHandler)// 自定义logoutSuccessHandler
                .and()
                .addFilterAt(new JwtTokenAuthenticationFilter(tokenComponent, userDetailService), SecurityWebFiltersOrder.HTTP_BASIC);
        return http.build();*//*
    }*/

    /**
     * BCrypt密码编码
     */
    @Bean("passwordEncoder")
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    /**
     * 注册用户信息验证管理器，可按需求添加多个按顺序执行
     */
    @Bean
    ReactiveAuthenticationManager reactiveAuthenticationManager() {
        LinkedList<ReactiveAuthenticationManager> managers = new LinkedList<>();
        managers.add(authentication -> {
            // 其他登陆方式 (比如手机号验证码登陆) 可在此设置不得抛出异常或者 Mono.error
            return Mono.empty();
        });
        // 必须放最后不然会优先使用用户名密码校验但是用户名密码不对时此 AuthenticationManager 会调用 Mono.error 造成后面的 AuthenticationManager 不生效
        managers.add(new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService));
        managers.add(tokenAuthenticationManager);
        return new DelegatingReactiveAuthenticationManager(managers);
    }
}
